package com.hqd.ch03.v14.reader.xml.parse.spport;

import com.hqd.ch03.v14.config.BeanDefinition;
import com.hqd.ch03.v14.config.ConstructorArgumentValues;
import com.hqd.ch03.v14.config.MutablePropertyValues;
import com.hqd.ch03.v14.reader.xml.namespace.NamespaceHandler;
import com.hqd.ch03.v14.reader.xml.namespace.NamespaceHandlerResolver;
import com.hqd.ch03.v14.reader.xml.namespace.support.DefaultNamespaceHandlerResolver;
import com.hqd.ch03.v14.reader.xml.parse.BeanDefinitionDocumentReader;
import com.hqd.ch03.v14.registry.BeanDefinitionRegistry;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.CharacterData;
import org.w3c.dom.*;

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
    public static final String PROPERTY_ELEMENT = "property";
    public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
    public static final String INDEX_ATTRIBUTE = "index";
    public static final String TYPE_ATTRIBUTE = "type";
    public static final String DESCRIPTION_ELEMENT = "description";
    public static final String NAME_ATTRIBUTE = "name";
    public static final String BEAN_ELEMENT = "bean";
    public static final String ID_ATTRIBUTE = "id";
    public static final String CLASS_ATTRIBUTE = "class";
    public static final String SCOPE_ATTRIBUTE = "scope";
    public static final String LAZY_INIT_ATTRIBUTE = "lazy-init";
    public static final String INIT_METHOD_ATTRIBUTE = "init-method";
    public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
    public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method";
    public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean";
    public static final String REF_ATTRIBUTE = "ref";
    public static final String VALUE_ATTRIBUTE = "value";
    public static final String DEFAULT_VALUE = "default";
    private static final String SINGLETON_ATTRIBUTE = "singleton";
    protected NamespaceHandlerResolver resolver = new DefaultNamespaceHandlerResolver();

    @Override
    public void registerBeanDefinitions(Document doc, BeanDefinitionRegistry registry) {
        parseBeanDefinitions(doc.getDocumentElement(), registry);
    }

    /**
     * 解析xml标签为BeanDefinition
     *
     * @param root
     * @param registry
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionRegistry registry) {
        if (isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    /**
                     * 默认标签
                     */
                    if (isDefaultNamespace(ele)) {
                        processBeanDefinition(ele, registry);
                    } else {//自定义标签
                        parseCustomElement(ele, registry);
                    }
                }
            }
        } else {
            parseCustomElement(root, registry);
        }
    }

    /**
     * 转换bean底下的自定义标签和属性
     *
     * @param ele
     * @param originalDef
     * @return
     */
    private BeanDefinition decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinition originalDef) {
        NamedNodeMap attributes = ele.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            originalDef = decorateIfRequired(node, originalDef);
        }
        NodeList children = ele.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                originalDef = decorateIfRequired(node, originalDef);
            }
        }
        return originalDef;
    }

    private BeanDefinition decorateIfRequired(Node node, BeanDefinition originalDef) {
        String namespaceURI = node.getNamespaceURI();
        if (!isDefaultNamespace(namespaceURI)) {
            NamespaceHandler handler = resolver.resolve(namespaceURI);
            if (handler != null) {
                return handler.decorate(node, originalDef);
            }
        }
        return originalDef;
    }

    /**
     * 处理bean标签
     *
     * @param ele
     * @param registry
     */
    private void processBeanDefinition(Element ele, BeanDefinitionRegistry registry) {
        BeanDefinition bd = doProcessBeanDefinition(ele, registry);
        /**
         * 解析自定义标签
         */
        BeanDefinition beanDefinition = decorateBeanDefinitionIfRequired(ele, bd);
        registry.registerBeanDefinition(bd.getId(), beanDefinition);
    }

    protected BeanDefinition doProcessBeanDefinition(Element ele, BeanDefinitionRegistry registry) {
        BeanDefinition bd = new BeanDefinition();
        parseBeanDefinitionAttributes(ele, bd);
        NodeList childNodes = ele.getChildNodes();
        if (childNodes.getLength() != 0) {
            MutablePropertyValues pvs = new MutablePropertyValues();
            ConstructorArgumentValues cvs = new ConstructorArgumentValues();
            for (int i = 0; i < childNodes.getLength(); i++) {
                Node node = childNodes.item(i);
                if (node instanceof Element) {
                    Element element = (Element) node;
                    String tagName = element.getTagName();
                    if (StringUtils.equalsIgnoreCase(PROPERTY_ELEMENT, tagName)) {
                        parsePropertyElement(element, pvs);
                    } else if (StringUtils.equalsIgnoreCase(CONSTRUCTOR_ARG_ELEMENT, tagName)) {
                        parseConstructorArgElement(element, cvs);
                    } else if (StringUtils.equalsIgnoreCase(DESCRIPTION_ELEMENT, tagName)) {
                        parseDescriptionElement(ele, bd);
                    }
                }
            }
            bd.setProperties(pvs);
            bd.setConstructorArgumentValues(cvs);
        }
        return bd;
    }

    private void parseBeanDefinitionAttributes(Element ele, BeanDefinition bd) {
        bd.setId(ele.getAttribute(ID_ATTRIBUTE));
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
        String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
        bd.setLazyInit(isDefaultValue(lazyInit) ? true : Boolean.valueOf(lazyInit));
        bd.setBeanClass(ele.getAttribute(CLASS_ATTRIBUTE));
        String scope = ele.getAttribute(SCOPE_ATTRIBUTE);
        bd.setScope(StringUtils.isBlank(scope) ? SINGLETON_ATTRIBUTE : scope);
        bd.setInitMethodName(ele.getAttribute(INIT_METHOD_ATTRIBUTE));
        bd.setDestroyMethodName(ele.getAttribute(DESTROY_METHOD_ATTRIBUTE));
    }

    private void parseDescriptionElement(Element element, BeanDefinition bd) {
        StringBuilder sb = new StringBuilder();
        NodeList nl = element.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node item = nl.item(i);
            if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
                sb.append(item.getNodeValue());
            }
        }
        bd.setDescription(sb.toString());
    }

    private void parseConstructorArgElement(Element element, ConstructorArgumentValues cvs) {
        String indexStr = element.getAttribute(INDEX_ATTRIBUTE);
        String nameStr = element.getAttribute(NAME_ATTRIBUTE);
        String value = element.getAttribute(VALUE_ATTRIBUTE);
        ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder();
        if (StringUtils.isNotBlank(indexStr)) {
            try {
                int index = Integer.parseInt(indexStr);
                valueHolder.setIndex(index);
            } catch (NumberFormatException e) {
                throw new RuntimeException(element.getTagName() + " Attribute 'index' of tag 'constructor-arg' must be an integer");
            }
        }
        if (StringUtils.isNotBlank(nameStr)) {
            valueHolder.setName(nameStr);
        }
        if (StringUtils.isNotBlank(value)) {
            valueHolder.setValue(value);
        } else {
            value = element.getAttribute(REF_ATTRIBUTE);
            if (StringUtils.isBlank(value)) {
                throw new RuntimeException(" tag 'constructor-arg' must have 'ref' or 'value' attribute ");
            }
            valueHolder.setValue(value);
            valueHolder.setRef(true);
        }
        cvs.addConstructorValue(valueHolder);
    }

    /**
     * 转换bean下的property节点
     *
     * @param element
     * @return
     */
    private void parsePropertyElement(Element element, MutablePropertyValues propertyValues) {
        boolean hasValueAttr = element.hasAttribute(VALUE_ATTRIBUTE);
        boolean hasRefAttr = element.hasAttribute(REF_ATTRIBUTE);
        if (hasValueAttr && hasRefAttr) {
            throw new RuntimeException(element.getTagName()
                    + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element");
        }
        String name = element.getAttribute(NAME_ATTRIBUTE);
        if (hasRefAttr) {
            propertyValues.addRefProperty(name, element.getAttribute(REF_ATTRIBUTE));
        } else {
            propertyValues.addProperty(name, element.getAttribute(VALUE_ATTRIBUTE));
        }
    }

    /**
     * 调用自定义解析器
     *
     * @param ele
     * @param registry
     */
    private void parseCustomElement(Element ele, BeanDefinitionRegistry registry) {
        NamespaceHandler resolve = resolver.resolve(getNamespaceURI(ele));
        BeanDefinition bd = resolve.parse(ele);
        registry.registerBeanDefinition(bd.getId(), bd);
    }

    /**
     * 判断是否是spring-bean的标签
     *
     * @param namespaceUri
     * @return
     */
    public boolean isDefaultNamespace(String namespaceUri) {
        return StringUtils.isBlank(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
    }

    /**
     * 判断是否是spring-bean的标签
     *
     * @param element
     * @return
     */
    public boolean isDefaultNamespace(Element element) {
        return isDefaultNamespace(getNamespaceURI(element));
    }

    private boolean isDefaultValue(String value) {
        return StringUtils.isBlank(value) || DEFAULT_VALUE.equals(value);
    }

    public String getNamespaceURI(Node node) {
        return node.getNamespaceURI();
    }
}
